package com.gitee.wsl.android.ext

import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import timber.log.Timber
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean

fun <T> MutableLiveData<List<T>>.addAll(values: List<T>) {
    val value = this.value?.toMutableList() ?: arrayListOf()
    value.addAll(values)
    this.value = value
}

fun <T> MutableLiveData<List<T>>.add(value: T) {
    val values = this.value?.toMutableList() ?: arrayListOf()
    values.add(value)
    this.value = values
}

internal fun <T1, T2, R> LiveData<T1>.combineLatest(
    other: LiveData<T2>,
    func: (T1, T2) -> R
): LiveData<R> {
    return MediatorLiveData<R>().apply {
        var lastA: T1? = null
        var lastB: T2? = null

        addSource(this@combineLatest) {
            lastA = it
            val observedB = lastB
            if (it == null && value != null) {
                value = null
            } else if (it != null && observedB != null) {
                value = func(it, observedB)
            }
        }

        addSource(other) {
            lastB = it
            val observedA = lastA
            if (it == null && value != null) {
                value = null
            } else if (observedA != null && it != null) {
                value = func(observedA, it)
            }
        }
    }
}

internal fun <T1, T2> LiveData<T1>.combineLatest(other: LiveData<T2>): LiveData<Pair<T1, T2>> {
    return combineLatest(other) { a, b -> a to b }
}

// Unlike built-in extension operation is performed on a provided thread pool.
// This is needed in our case since we compare requests and responses which can be big
// and result in frame drops.
internal fun <T> LiveData<T>.distinctUntilChanged(
    executor: Executor = ioExecutor(),
    areEqual: (old: T, new: T) -> Boolean = { old, new -> old == new }
): LiveData<T> {
    val distinctMediator = MediatorLiveData<T>()
    var old = uninitializedToken
    distinctMediator.addSource(this) { new ->
        executor.execute {
            @Suppress("UNCHECKED_CAST")
            if (old === uninitializedToken || !areEqual(old as T, new)) {
                old = new
                distinctMediator.postValue(new)
            }
        }
    }
    return distinctMediator
}

private val uninitializedToken: Any? = Any()

// It is lesser evil than providing a custom executor.
@SuppressLint("RestrictedApi")
private fun ioExecutor() = ArchTaskExecutor.getIOThreadExecutor()

/** Combines a LiveData<T> with a LiveData<K> and returns a LiveData<R>. */
fun <T, K, R> LiveData<T>.combineWith(liveData: LiveData<K>, block: (T?, K?) -> R): LiveData<R> {
    val result = MediatorLiveData<R>()
    result.addSource(this) { result.value = block(this.value, liveData.value) }
    result.addSource(liveData) { result.value = block(this.value, liveData.value) }
    return result
}

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 *
 *
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 *
 *
 * Note that only one observer is going to be notified of changes.
 * https://github.com/android/architecture-samples/blob/166ca3a93ad14c6e224a3ea9bfcbd773eb048fb0/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
 */
class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Timber.w("Multiple observers registered but only one will be notified of changes." )
        }

        // Observe the internal MutableLiveData
        super.observe(
            owner
        ) { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    @MainThread
    override fun setValue( t: T?) {
        pending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

class BroadcastReceiverLiveData<T>(
    context: Context,
    private val intentFilter: IntentFilter,
    private val transformFunction: (Context, Intent) -> T
): LiveData<T>() {

    private val context = context.applicationContext
    private val receiver = object: BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val newValue = transformFunction(context, intent)
            postValue(newValue)
        }

    }

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    override fun onActive() {
        super.onActive()
        context.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
    }

    override fun onInactive() {
        super.onInactive()
        context.unregisterReceiver(receiver)
    }
}

fun <T> Context.BroadcastReceiverLiveData(
     intentFilter: IntentFilter,
     transformFunction: (Context, Intent) -> T
    ): LiveData<T> =  BroadcastReceiverLiveData(this,intentFilter, transformFunction)